package xman.mssh.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.net.ProtocolCommandEvent;
import org.apache.commons.net.ProtocolCommandListener;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FTPClientPool {
    
    static Logger logger = LoggerFactory.getLogger(FTPClientPool.class);
    
    private String host;
    
    private String username;
    
    private String password;
    
    private int connectTimeount;
    
    private int maxActive;
    
    private Boolean activeMode;

    @SuppressWarnings("rawtypes")
    private GenericObjectPool clientPool = null;
    
    public FTPClientPool(String host, String username, String password, int connectTimeout, int maxActive,
            boolean activeMode) {
        this.host = host;
        this.username = username;
        this.password = password;
        this.connectTimeount = connectTimeout;
        this.maxActive = maxActive;
        clientPoolInitialize();
    }
    
    @SuppressWarnings({ "unchecked", "rawtypes" })
    private void clientPoolInitialize() {
        if (logger.isInfoEnabled()) {
            logger.info("clientPoolPropertiesInit start");
        }
        
        clientPool = new GenericObjectPool(new ClientPoolObjectFactory());

        clientPool.setTestOnBorrow(true);
        clientPool.setTestWhileIdle(true);
        
        if (maxActive <= 0) {
            maxActive = Runtime.getRuntime().availableProcessors() * 8;
        }
        clientPool.setMaxActive(maxActive);
        clientPool.setMaxIdle((int) (clientPool.getMaxActive() / 2));
        
        clientPool.setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_BLOCK);
    }
    
    @SuppressWarnings("rawtypes")
    class ClientPoolObjectFactory implements PoolableObjectFactory {
        
        public void activateObject(Object obj) throws Exception {
            if (logger.isDebugEnabled()) {
                FTPClient client = (FTPClient) obj;
                if (client != null && client.isConnected()) {
                    logger.debug("#activate status {} {} ", client.getRemoteAddress(), client.getLocalPort());
                }
            }
        }
        
        public void destroyObject(Object obj) throws Exception {
            FTPClient client = (FTPClient) obj;
            if (client != null && client.isConnected()) {
                try {
                    client.logout();
                    client.disconnect();
                } catch (Throwable e) {
                    logger.info("destroyObject failed {}", e);
                }
            }
        }
        
        public Object makeObject() throws Exception {
            FTPClient instance = newFTPClientInstance();
            return instance;
        }
        
        public void passivateObject(Object obj) throws Exception {
            if (logger.isDebugEnabled()) {
                FTPClient client = (FTPClient) obj;
                if (client != null && client.isConnected()) {
                    logger.debug("#passivate status {} {} ", client.getRemoteAddress(), client.getLocalPort());
                }
            }
        }
        
        public boolean validateObject(Object obj) {
            FTPClient client = (FTPClient) obj;
            if (client != null && client.isConnected() && client.isAvailable()) {
                if (logger.isDebugEnabled()) {
                    try {
                        logger.debug("validate : {}", client.getStatus());
                    } catch (Throwable e) {
                        e.printStackTrace();
                    }
                }
                return true;
            }
            return false;
        }
    }
    
    private FTPClient newFTPClientInstance() throws Exception {
        FTPClient instance = new FTPClient();
        instance.addProtocolCommandListener(new ProtocolCommandListener() {
            public void protocolReplyReceived(ProtocolCommandEvent event) {
                logger.trace("#recv : {}", event.getMessage());
            }
            
            public void protocolCommandSent(ProtocolCommandEvent event) {
                logger.trace("#sent : {}", event.getMessage());
            }
        });
        instance.setConnectTimeout(this.connectTimeount);
        instance.connect(host);
        int reply = instance.getReplyCode();
        if (!FTPReply.isPositiveCompletion(reply)) {
            instance.logout();
            instance.disconnect();
            throw new RuntimeException("Connecting to FTP Server error");
        }
        instance.login(username, password);
        instance.setFileType(FTP.BINARY_FILE_TYPE);
        if (this.activeMode)
            instance.enterLocalActiveMode();
        else
            instance.enterLocalPassiveMode();
        return instance;
    }

    public Object get() {
        try {
            return clientPool.borrowObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    @SuppressWarnings("unchecked")
    public boolean release(Object obj) {
        if (obj == null) {
            return false;
        }
        try {
            clientPool.returnObject(obj);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
    
    public boolean uploadFile(String localFileFullName, String fileName, String hostDir) throws Exception {
        BufferedInputStream bis = null;
        FTPClient instance = null;
        boolean status = false;
        try {
            instance = (FTPClient) this.get();
            String fullFileName = hostDir + fileName;
            bis = new BufferedInputStream(new FileInputStream(localFileFullName));
            mkdirs(substringBeforeLast(fullFileName, "/"), instance);
            status = instance.storeFile(fullFileName, bis);
            logger.info("upload ret : {}", status);
            return status;
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            this.release(instance);
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return status;
    }
    
    private static String substringBeforeLast(String str, String separator) {
        if (StringUtils.isEmpty(str) || StringUtils.isEmpty(separator)) {
            return str;
        }
        int pos = str.lastIndexOf(separator);
        if (pos == -1) {
            return str;
        }
        return str.substring(0, pos);
    }

    public boolean downloadFile(String fileName, String remoteFileName) {
        BufferedOutputStream bos = null;
        FTPClient instance = null;
        boolean status = false;
        try {
            instance = (FTPClient) this.get();
            bos = new BufferedOutputStream(new FileOutputStream(fileName));
            status = instance.retrieveFile(remoteFileName, bos);
            logger.info("download ret : {}", status);
            return status;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            this.release(instance);
            if (bos != null) {
                try {
                    bos.flush();
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return status;
    }

    private void mkdirs(String dir, FTPClient instance) throws IOException {
        String currentDir = dir;
        while (!instance.changeWorkingDirectory(currentDir.isEmpty() ? "/" : currentDir)) {
            currentDir = substringBeforeLast(currentDir, "/");
        }
        
        if (currentDir.equals(dir)) {
            return;
        }
        
        String append = dir.substring(currentDir.length());
        String[] split = append.split("/");
        for (String str : split) {
            currentDir += "/" + str;
            instance.mkd(currentDir);
        }
    }
    
    public static void main(String[] args) throws Exception {
        final FTPClientPool client = new FTPClientPool("192.168.1.220", "karakal", "karakal", 10000, 3, false);
        final String dir = "D:\\Users\\usr\\Music\\Notting Hill\\";
        File file = new File(dir);
        if (file.isDirectory()) {
            String[] files = file.list();
            for (Object o : files) {
                final String f = (String) o;
                new Thread(new Runnable() {
                    public void run() {
                        try {
                            client.uploadFile(dir + f, f, "/data/music/dest/tmp/");
                        } catch (Throwable e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        }
    }
}
